<?php

namespace Piz;

class Server
{
    private static $instance;
    public $server;

    private $config;
    private $workerId;
    public $name;

    private function __construct()
    {
    }

    private function __clone()
    {
    }

    public static function get_instance()
    {
        if (is_null(self::$instance)) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function set_config($config)
    {
        $this->config = $config;
        $this->name = $config['name'];
    }

    public function run()
    {
        $swoole_server = isset($this->config['server']) && $this->config['server'] == 'websocket' ? 'swoole_websocket_server' : 'swoole_http_server';
        $this->config['ip'] = $ip = isset($this->config['ip']) && ip2long($this->config['ip']) ? $this->config['ip'] : '0.0.0.0';
        $this->config['port'] = $port = isset($this->config['port']) && intval($this->config['port']) ? $this->config['port'] : 9527;
        $this->server = new $swoole_server($ip, $port);

        $this->server->set($this->config['set']);

        $this->server->on('start', [$this, 'onStart']);
        $this->server->on('workerstart', [$this, 'onWorkerStart']);
        $this->server->on('workerstop', [$this, 'onWorkerStop']);
        $this->server->on('workererror', [$this, 'onWorkerError']);
        $this->server->on('managerStart', [$this, 'onManagerStart']);
        $this->server->on('managerStop', [$this, 'onManagerStop']);

        if ($this->config['server'] == 'websocket') {
            $this->server->on('open', [$this, 'onOpen']);
            $this->server->on('message', [$this, 'onMessage']);
            $this->server->on('close', [$this, 'onClose']);
        }
        $this->server->on('request', [$this, 'onRequest']);
        if (isset($this->config['set']['task_worker_num']) && $this->config['set']['task_worker_num'] > 0) {
            $this->server->on('task', [$this, 'onTask']);
            $this->server->on('finish', [$this, 'onFinish']);
        }
        $this->server->start();
    }

    public function onStart($server)
    {
        date_default_timezone_set('Asia/Shanghai');
        $this->set_process_title($this->name . '-master');
        Log::get_instance()->write('INFO', $this->name, "启动成功", "{$this->config['ip']}:{$this->config['port']}");
        Hook::get_instance()->listen('start', $server);
    }

    /**
     * Worker进程/Task进程启动时
     */
    public function onWorkerStart($server, $workder_id)
    {
        date_default_timezone_set('Asia/Shanghai');
        $this->workerId = $workder_id;
        if (function_exists('apc_clear_cache')) {
            apc_clear_cache();
        }
        if (function_exists('opcache_reset')) {
            opcache_reset();
        }
        // 重新加载配置
        $this->reload_config();
        if (!$server->taskworker) {//worker进程
            $this->set_process_title($this->name . "-worker");
        } else {
            $this->set_process_title($this->name . "-tasker");
        }

        //每3秒执行一次
        //swoole_timer_tick(3000, function ($time_id) {
        //    Log::get_instance()->save();
        //});
    }

    /**
     * 当worker/task_worker进程发生异常
     */
    public function onWorkerError($server, $worker_id, $worker_pid, $exit_code)
    {
        Log::get_instance()->write('ERROR', $this->name, "进程异常", "WorkerID:{$worker_id}", "WorkerPID:{$worker_pid}", "ExitCode:{$exit_code}");
    }

    /**
     * worker进程终止时
     * @param  $server
     * @param  $worker_id
     */
    public function onWorkerStop($server, $worker_id)
    {
        Log::get_instance()->write('ERROR', $this->name, "进程终止", "WorkerID:{$worker_id}");
    }

    /**
     * 当管理进程启动时
     * @param $serv
     */
    public function onManagerStart($serv)
    {
        $this->set_process_title($this->name . '-manager');
        Log::get_instance()->write('INFO', $this->name, "管理进程启动");
    }

    /**
     * 当管理进程结束时
     * @param $serv
     */
    public function onManagerStop($serv)
    {
        Log::get_instance()->write('INFO', $this->name, "管理进程结束");
    }

    public function onOpen($server, $request)
    {
        Log::get_instance()->write('DEBUG', "FD:{$request->fd}", "握手成功");
        Hook::get_instance()->listen('open', $server, $request->fd);
    }

    public function onRequest($request, $response)
    {
        Log::get_instance()->write('DEBUG', $request, $response);
        if ($this->config['set']['enable_static_handler'] && $request->server['request_uri'] == '/favicon.ico') {
            return;
        }
        App::get_instance()->http($this->server, $request, $response);
    }

    public function onMessage($server, $frame)
    {
        Log::get_instance()->write('DEBUG', "FD:{$frame->fd}", "Opcode:{$frame->opcode}", "Finish:{$frame->finish}", "Data:{$frame->data}");
        App::get_instance()->websocket($server, $frame);
        /*$fds = [];
        foreach ($server->connections as $client_fd) {
            if ($frame->fd == $client_fd && $server->exist($client_fd)) {
                $server->push($client_fd, $frame);
                $fds[] = $client_fd;
            }
        }*/
        //$server->push($frame->fd, $frame);
    }

    public function onTask($server, $task_id, $workder_id, $data)
    {
        return Task::get_instance()->set_server($server)->dispatch($task_id, $workder_id, $data);
    }

    public function onFinish($server, $task_id, $data)
    {
        Task::get_instance($server)->finish($task_id, $data);
    }

    public function onClose($server, $fd)
    {
        Log::get_instance()->write('DEBUG', "FD:{$fd}", "关闭连接");
        Hook::get_instance()->listen('close', $server, $fd);
    }

    /**
     * 获取 来自哪个监听端口
     * @param $fd
     * @return mixed
     */
    public function get_server_port($fd)
    {
        return $this->server->connection_info($fd)['server_port'];
    }

    /**
     * 重新加载配置
     */
    public function reload_config()
    {
        $this->config = config('app');
        $this->name = $this->config['name'];
    }

    /**
     * Set process name.
     * @param string $title
     * @return void
     */
    public function set_process_title($title)
    {
        if (PHP_OS === 'Darwin') return;
        // >=php 5.5
        if (function_exists('cli_set_process_title')) {
            @cli_set_process_title($title);
        } else {
            @swoole_set_process_name($title);
        }
    }
}
